#####################################################################
# File:         Environment.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the Environment class, the base
#               container for Knowledge Models.  Environment objects
#               define the common/shared environment information for
#               a collection of Knowledge Models, including but not
#               limited to security and database connections.
#
# See Also:     Model
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.KnowledgeEngineCore import *
from Products.KnowledgeEngine.ClassInfo import ClassInfo
from Products.KnowledgeEngine.PropertyDefinition import PropertyDefinition
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.GenericContainer import GenericContainer, GenericContainerInfo
from Products.KnowledgeEngine.Messaging.Messaging import *
from Products.KnowledgeEngine.Messaging.DebugMessageListener import DebugMessageListener

#####################################################################
## Zope Library Imports
from AccessControl import ClassSecurityInfo
from Acquisition import Implicit
from Globals import Persistent, MessageDialog, DTMLFile
from AccessControl.Role import RoleManager
from OFS.SimpleItem import Item
from OFS.PropertyManager import PropertyManager
from OFS.ObjectManager import ObjectManager
from OFS import XMLExportImport
from ImageFile import ImageFile
from Products.ZSQLMethods.SQL import SQL
from ZODB import ExportImport

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## EnvironmentInfo Class Definition
class EnvironmentInfo(GenericContainerInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'Environment'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Knowledge Environment"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            NOTE: this is not used as Environments don't have
            identities
        """

        return "env"
    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == 'Environment' or GenericContainerInfo.isInstanceOf(self, className)

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            ABSTRACT: Return the appropriate icon file for the class.
            All concrete classes must provide an icon!
        """

        return ImageFile('www/icon.gif', globals())
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """

        #DEFAULT RULE: Environments are not permitted in any KnowledgeEngine containers
        return false

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #DEFAULT RULE: Environments may contain anything, BUT Components AND Reports
        #TODO: Actually the rule is anything that is not allowed anywhere in Models!
        return not childClassInfo.isInstanceOf('Component') and not childClassInfo.isInstanceOf('Report')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        GenericContainerInfo.onConfigureProperties(self)
        
        #add the 'description' property        
        self.addPropertyDefinition(PropertyDefinition(identity="description", displayName="Description", description="The description of the environment", propertyFormat=pf_TEXT_MULTILINE))        

        #add the 'dbconnection' property        
        self.addPropertyDefinition(PropertyDefinition(identity="dbconnection", displayName="Database", description="The environment storage database", propertyFormat=pf_NATIVE, hasEnumeratedValues=true, enumeratedValuesFunction=(lambda context:[dbid for dbid in context.SQLConnectionIDs()])))        

    #---------------------------------------------------------------#

#####################################################################
## Environment Class Definition
class Environment(GenericContainer):
    """
        Base container for all Knowledge Engine Models
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = EnvironmentInfo()

    #---------------------------------------------------------------#
    # Default Attribute Values

    #a volatile dictionary of references to Libraries with in the
    #Environment indexed by their identity
    _v_Libraries = {}

    #a volatile dictionary of references to Models with in the
    #Environment indexed by their identity
    _v_Models = {}

    #a volatile reference to the user access manager for this environment
    _v_UserAccessManager = None

    #the default Messaging System
    messaging = None

    #---------------------------------------------------------------#
    # Security
    #
    # There are the following Permissions
    # View Models - to view Models and take assessments
    # View Reports - to view Environment reports and reports of other users
    # View management screens - the default Zope Permission for all management

    security = ClassSecurityInfo()
    security.setPermissionDefault('View Models',['Authenticated'])
    security.setPermissionDefault('View Reports',['Manager'])

    #---------------------------------------------------------------#
    # DTML Docs
    security.declareProtected('View Models', 'home')
    home = DTMLFile('dtml/home', globals())

    security.declareProtected('View Models', 'home_reports')
    home_reports = DTMLFile('dtml/home_reports', globals())

    security.declareProtected('View Models', 'home_reports_locked')
    home_reports_locked = DTMLFile('dtml/home_reports_locked', globals())    
    
    security.declareProtected('View Models', 'home_css')
    home_css = DTMLFile('dtml/home_css', globals())

    security.declareProtected('View Models', 'home_instructions')
    home_instructions = DTMLFile('dtml/home_instructions', globals())
    
    security.declareProtected('View Reports', 'reports')
    reports = DTMLFile('dtml/reports', globals())

    security.declareProtected('View Reports', 'reports_css')
    reports_css = DTMLFile('dtml/reports_css', globals())
    
    security.declareProtected('View Reports', 'reports_by_staff_inprocess')
    reports_by_staff_inprocess = DTMLFile('dtml/reports_by_staff_inprocess', globals())

    security.declareProtected('View Reports', 'reports_by_staff_locked')
    reports_by_staff_locked = DTMLFile('dtml/reports_by_staff_locked', globals())

    security.declareProtected('View Reports', 'reports_by_model')
    reports_by_model = DTMLFile('dtml/reports_by_model', globals())

    security.declareProtected('View Reports', 'reports_by_model_inprocess')
    reports_by_model_inprocess = DTMLFile('dtml/reports_by_model_inprocess', globals())

    security.declareProtected('View Reports', 'reports_by_model_locked')
    reports_by_model_locked = DTMLFile('dtml/reports_by_model_locked', globals())

    security.declareProtected('View Reports', 'reports_by_model_grouped')
    reports_by_model_grouped = DTMLFile('dtml/reports_by_model_grouped', globals())

    security.declareProtected('View Reports', 'reports_by_model_datadump')
    reports_by_model_datadump = DTMLFile('dtml/reports_by_model_datadump', globals())
    
    #---------------------------------------------------------------#

    def __init__(self, id, identity, title, description, dbconnection):
        "Constructor for the object"

        GenericContainer.__init__(self, id, identity)

        self.description = description
        self.dbconnection = dbconnection

        #perform custom onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """
        #set the volatile attributes
        self._v_Libraries = {}
        self._v_Models = {}
        self._v_UserAccessManager = None
        
        #perform default onAfterInstanciation for the container
        GenericContainer.onAfterInstanciation(self)

        #automatically call the ClassInfo.onAfterEnvironmentInstanciated() method
        #for all of the registered classes given this Environment
        for classInfo in getRegisteredClassInfo():
            classInfo.onAfterEnvironmentInstanciated(self)

    #---------------------------------------------------------------#

    def getDescription(self):
        "Returns the Description of the model"
        return self.description

    #---------------------------------------------------------------#

    def setDescription(self, description):
        "Sets the Description of the model"
        self.description = description

    #---------------------------------------------------------------#

    def getDBConnection(self):
        "Return the Database Connection"
        return self.dbconnection

    #---------------------------------------------------------------#

    def getAbsolutePath(self):
        "Return the path to the object in the environment"

        #get that absolute path of the parent
        lPath = self.getParent().absolute_url(relative=1) + "/" + self.getId()
        return lPath

    #---------------------------------------------------------------#

    def getEnvironment(self):
        """
            Return this environment.  This method is the base
            case for the recursive getEnvironment lookup in
            GenericObject.getEnvironment()
        """

        return self

    #---------------------------------------------------------------#

    def getDatabaseConnection(self):
        """
            Return Connection object for the environment database.
        """

        #attempt to locate the Zope Database Connection for the environment
        zda = getattr(self, self.dbconnection)

        #get and return a real connection from the zda
        return zda()

    #---------------------------------------------------------------#

    def getZopeMetaTypes(self):
        """
            Returns specialized Zope all_meta_types tuple
            for the class providing class info.
            
            NOTE: override this method to provide specialized
            all_meta_types tuple for a class.
        
            NOTE: Always include the super class result of this
            method when overriding this method.
        """

        #get the parent's meta types
        parent = self.getParent()                
        if callable(parent.all_meta_types):
            metaTypes = parent.all_meta_types()
        else:
            metaTypes = parent.all_meta_types
                                            
        #ensure that the parent's meta types don't contain the KnowledgeEngine product
        return tuple([x for x in metaTypes if not ((x.has_key('product') and x['product'] == "KnowledgeEngine") or (x.has_key('name') and x['name'] == "Knowledge Environment"))])
    
    #---------------------------------------------------------------#

    def getMessaging(self):
        """
            Return the messaging system for this environment
        """

        #does the environment have a messaging system?
        if self.messaging == None:

            #create the messaging system, with this GenericObject as the owner
            self.messaging = Messaging(self)

        #return the messaging system
        return self.messaging

    #---------------------------------------------------------------#

    def _findModels(self, children = None):
        """
            Returns a dictionary of all the Model objects in this Environment
            (includes searching folderish objects), indexed by getIdentity
        """

        result = {}

        #any initial children provided?
        if children == None:
            children = self.getChildren()

        for child in children:
            if isinstance(child, GenericContainer):

                if child.isInstanceOf('Model'):
                    result.update({child.getIdentity():child})

                elif child.isInstanceOf('ElementContainer'):
                    result.update(self._findModels(child.getChildren()))

        return result

    #---------------------------------------------------------------#

    def rebuildModelsDictionary(self):
        """
            Attempt to rebuild the models dictionary, filling
            it with the models currently in the Environment.

            Note: the models dictionary is volitile, so we have
            to rebuild it when the environment is loaded/imported/copied.
        """

        ###print "DEBUG: Environment.rebuildModelsDictionary: Commenced"

        #clear the current dictionary
        self._v_Models.clear()

        #get and filter the immediate children of the environment to find the models.
        self._v_Models.update(self._findModels())

        ###print "DEBUG: Environment.rebuildModelsDictionary: Completed for %d Models" % len(self._v_Models)

    #---------------------------------------------------------------#

    def getModels(self, sortedByName = true):
        """
            Returns a list of the Models in this Environment.
            (sorted by model name)
        """

        #does the model dictionary contain anything?
        if len(self._v_Models) == 0:
            self.rebuildModelsDictionary()

        #get the models from the dictionary
        models = self._v_Models.values()

        #sort it?
        if sortedByName:
            models.sort(lambda x, y : -(x.getTitleOrId() < y.getTitleOrId()))

        return models

    #---------------------------------------------------------------#

    def getModel(self, modelIdentity):
        """
            Returns the specified model given its identity.
            Returns None if no such model exists.
        """

        #does the model dictionary contain anything?
        if len(self._v_Models) == 0:
            self.rebuildModelsDictionary()

        #does the environment contain the model?
        if self._v_Models.has_key(modelIdentity):
            return self._v_Models[modelIdentity]
        else:
            return None

    #---------------------------------------------------------------#

    def onAfterAddModel(self, model):
        """
            Handle when a Model has beed added somewhere in the
            Environment.

            Called by Model.onAfterAdd and Model.onAfterClone
        """

        ###print "DEBUG: Environment.onAfterAddModel: Commenced"

        #does the model dictionary contain anything?
        if len(self._v_Models) == 0:
            self.rebuildModelsDictionary()

        #add the model
        #(don't override previous entries with the same identity
        # so Cloning/Cut/Copy/Paste doesn't destroy existing entries)
        if not self._v_Models.has_key(model.getIdentity()):
            self._v_Models[model.getIdentity()] = model

        ###print "DEBUG: Environment.onAfterAddModel: Completed (%d)" % len(self._v_Models)

    #---------------------------------------------------------------#

    def onBeforeDeleteModel(self, model):
        """
            Handle when a Model is about to be deleted from
            somewhere with in the Environment.

            Called by Model.onBeforeDelete.
        """

        ###print "DEBUG: Environment.onBeforeDeleteModel: Commenced"

        #remove the specified component
        if self._v_Models.has_key(model.getIdentity()):
            del self._v_Models[model.getIdentity()]

        ###print "DEBUG: Environment.onBeforeDeleteModel: Completed (%d)" % len(self._v_Models)

    #---------------------------------------------------------------#

    def _findLibraries(self, children = None):
        """
            Returns a dictionary of all the Library objects in this Environment
            (includes searching folderish objects), indexed by getIdentity
        """

        result = {}

        #any initial children provided?
        if children == None:
            children = self.getChildren()

        for child in children:
            if isinstance(child, GenericContainer):

                if child.isInstanceOf('Library'):
                    result.update({child.getIdentity():child})

                elif child.isInstanceOf('ElementContainer'):
                    result.update(self._findLibraries(child.getChildren()))

        return result

    #---------------------------------------------------------------#

    def rebuildLibrariesDictionary(self):
        """
            Attempt to rebuild the libraries dictionary, filling
            it with the libraries currently in the Environment.

            Note: the models dictionary is volitile, so we have
            to rebuild it when the environment is loaded/imported/copied.
        """

        ###print "DEBUG: Environment.rebuildLibrariesDictionary: Commenced"

        #clear the current dictionary
        self._v_Libraries.clear()

        #get and filter the immediate children of the environment to find the Libraries.
        self._v_Libraries.update(self._findLibraries())

        ###print "DEBUG: Environment.rebuildLibrariesDictionary: Completed for %d Libraries" % (len(self._v_Libraries),)

    #---------------------------------------------------------------#

    def getLibraries(self, sortedByName = true):
        """
            Returns a list of the Libraries in this Environment.
            (sorted by library name)
        """

        #does the library dictionary contain anything?
        if len(self._v_Libraries) == 0:
            self.rebuildLibrariesDictionary()

        #get the libraries from the dictionary
        libraries = self._v_Libraries.values()

        #sort it?
        if sortedByName:
            libraries.sort(lambda x, y : -(x.getTitleOrId() < y.getTitleOrId()))

        return libraries

    #---------------------------------------------------------------#

    def getLibrary(self, libraryIdentity):
        """
            Returns the specified library given its identity.
            Returns None if no such library exists.
        """

        #does the library dictionary contain anything?
        if len(self._v_Libraries) == 0:
            self.rebuildLibrariesDictionary()

        ###print "DEBUG: Environment.getLibrary: Get Library %s from %s" % (libraryIdentity,repr(self._v_Libraries))

        #does the environment contain the model?
        if self._v_Libraries.has_key(libraryIdentity):
            return self._v_Libraries[libraryIdentity]
        else:
            return None

    #---------------------------------------------------------------#

    def onAfterAddLibrary(self, library):
        """
            Handle when a Library has beed added somewhere in the
            Environment.

            Called by Library.onAfterAdd and Library.onAfterClone
        """

        ###print "DEBUG: Environment.onAfterAddLibrary: Commenced"

        #does the library dictionary contain anything?
        if len(self._v_Libraries) == 0:
            self.rebuildLibrariesDictionary()

        #add the library
        #(don't override previous entries with the same identity
        # so Cloning/Cut/Copy/Paste doesn't destroy existing entries)
        if not self._v_Libraries.has_key(library.getIdentity()):
            self._v_Libraries[library.getIdentity()] = library

        ###print "DEBUG: Environment.onAfterAddLibrary: Completed (%d)" % len(self._v_Libraries)

    #---------------------------------------------------------------#

    def onBeforeDeleteLibrary(self, library):
        """
            Handle when a Library is about to be deleted from
            somewhere with in the Environment.

            Called by Library.onBeforeDelete.
        """

        ###print "DEBUG: Environment.onBeforeDeleteLibrary: Commenced"

        #does the library dictionary contain anything?
        if len(self._v_Libraries) == 0:
            self.rebuildLibrariesDictionary()

        #remove the specified library
        if self._v_Libraries.has_key(library.getIdentity()):
            del self._v_Libraries[library.getIdentity()]

        ###print "DEBUG: Environment.onBeforeDeleteLibrary: Completed (%d)" % len(self._v_Libraries)

    #---------------------------------------------------------------#

    def containsLibraryComponents(self):
        """
            Returns if the environment contains one or more libraries
            that in turn contain components.
        """

        return len(filter(lambda x : x.containsChildren(), self.getLibraries())) > 0

    #---------------------------------------------------------------#

    def registerUserAccessManager(self, userAccessManager):
        """
            Notifies the environment of a new user access manager
        """

        self._v_UserAccessManager = userAccessManager

        ###print "DEBUG: Environment.registerUserAccessManager: Completed"        

    #---------------------------------------------------------------#

    def deregisterUserAccessManager(self, userAccessManager):
        """
            Notifies the environment of a deleted user access manager
        """

        self._v_UserAccessManager = None

        ###print "DEBUG: Environment.deregisterUserAccessManager: Completed"        
        
    #---------------------------------------------------------------#

    def getUserAccessManager(self):
        """
            Returns the UserAccessRules object of the Environment
            or None if one does not exist.
        """

        #have we already got a reference to the user access manager for this environment?
        if self._v_UserAccessManager == None:

            #get objects that implement the UserAccessManager class/interface
            userAccessManagers = self.getChildrenByInstanceOf('UserAccessManager')

            if len(userAccessManagers) == 0:
                self._v_UserAccessManager = None
            else:
                self._v_UserAccessManager = userAccessManagers[0]

        return self._v_UserAccessManager
        
    #---------------------------------------------------------------#

    def getUserFolders(self, o=None):
        """
            Returns a list of all the UserFolders in this Environment
        """

        #ret = []

        #if o == None:
        #    o = self

        #for x in o.objectValues():
        #    if hasattr(x,'isAUserFolder'):
        #        ret = ret + [x]
        #    if hasattr(x,'isPrincipiaFolderish'):
        #        ret = ret + self.getUserFolders(x)
        #
        #Ive cut this down to only return the root userfolder as I
        #feel that mutiple user folders will cause problems

        uf=getattr(self,'acl_users')

        return [uf]
        
    #---------------------------------------------------------------#

    def getUserFolder(self):
        """
            Returns the immediate UserFolders in this Environment
        """

        return getattr(self,'acl_users')
        
    #---------------------------------------------------------------#

    def getUserFolderType(self):
        """
            Returns the type of user folder
        """

        uf=self.getUserFolder()
        if uf.title=='Extensible User Folder':
            ufType='exUserFolder'
        else:
            ufType='basicUserFolder'

        return ufType

    #---------------------------------------------------------------#

    def getAllUsers(self):
        """
            Returns a list of all the Users in this Environment
        """

        ret = []

        for uf in self.getUserFolders():
            ret = ret + uf.getUsers()
        return ret

    #---------------------------------------------------------------#

    def prepareAssessment(self,userName,modelIdentity=None,aName=None,setComponents={}):
        """
            Called by the /home method. Automatically
            creates a new assessment of the given name and then sets
            any of the components in setComponents to the given values.
            Returns nothing
        """

        #has a model identity been provided?
        if modelIdentity != None:
            
            #get the model for the identity specified
            model = self.getModel(modelIdentity)
        
            #does the model exist?
            if model != None:

                #get the user access manager for the model
                uam = self.getUserAccessManager()
                
                #get the user access rules for the model
                uar = uam.getUserAccessRules(userName)
                
                #get the assessments for the user
                assessments=uar.getAssessments(modelIdentity)

                #check to see if the name exists
                found = false
                for a in assessments:
                    if a[1]['name']==aName:
                        found=1
                        assessmentIdentity=a[0]
                        
                #if not then create it
                if not found:
                    uar.addAssessment(modelIdentity,aName)
                    
                    #now get its id back
                    for a in uar.getAssessments(modelIdentity):
                        if a[1]['name']==aName:
                            assessmentIdentity=a[0]

                #now check for component settings and set if found
                for sc in setComponents.items():
                    c=model.getComponent(sc[0])
                    if c!=None:
                        c.setState(model,assessmentIdentity,sc[0],sc[1])

    #---------------------------------------------------------------#

    def dump(self, value):
        """
            Output the specified value to stdout
        """
        
        print value

    #---------------------------------------------------------------#

    customImporters = {
        XMLExportImport.magic: XMLExportImport.importXML,
        }

    # overrides Zope's default importExport form when in a KnowledgeEngine Environment
    # comment out the following line to revert to default importExport form
    manage_importExportForm = DTMLFile('dtml/manage_importExportForm', globals())

    def manage_importModel(self, REQUEST, set_owner=1):
        "import a model from the local drive"
        connection = self._p_jar
        
        while connection is None:
            connection = self._p_jar
        ob = connection.importFile(REQUEST.file, customImporters = self.customImporters)

        if REQUEST: self._verifyObjectPaste(ob, validate_src=0)
        id = ob.id
        if hasattr(id, 'im_func'):
            id = id()
        self._setObject(id, ob, set_owner=set_owner)
        ob = self._getOb(id)
        ob.manage_changeOwnershipType(explicit=0)
        
        # possibly add a check for meta_type to allow only upload of models

        if REQUEST is not None:
            return MessageDialog(
                title='Object Imported',
                message='<em>%s</em> was sucessfully imported' % id,
                action='manage_workspace'
                )
                
                
#####################################################################
## Class Initialisation

manage_addEnvironmentForm = DTMLFile('dtml/manage_addEnvironmentForm', globals())

def manage_addEnvironment(self, id, title="Knowledge - Environment", description="", dbconnection=None, REQUEST=None):
    """Create and add a environment to its container/folder"""
    
    identity = generateIdentity(Environment.identity_type)
    
    obj = Environment(id, identity, title, description, dbconnection) 
    
    self._setObject(id, obj)
    
    if REQUEST is not None:
        try: 
            url = self.DestinationURL()
        except: 
            url = REQUEST['URL1']        
        REQUEST.RESPONSE.redirect(url + '/manage_main')  

# Register Class Information with the Knowledge Engine
registerGenericObjectClass(Environment)

#####################################################################
